Gatsby + Stripe + Mantine UI でセキュアなECサイトをサクッと立ち上げる
どうも、ベルリンオフィスの小西です。
先日、知人のためにシンプルなECサイトを作る機会があり、GatsbyとStripeでサクッと作った構成が非常にうまくフィットしたため、今回はその構築方法をまとめてみます。
構成
- Gatsby … 静的サイト生成フレームワーク
- Stripe … 決済プロバイダー、商品情報の管理
- Mantine UI … UIライブラリ。サイトの見た目を整える
以上、という感じです。
見た目は↓こんな感じになります。
各商品の決済ボタン[Purchase]を押すと、商品情報を受け渡しつつStripeドメインにリダイレクトし、そちらで安全に決済を完了させます。
個人的に、クレカ支払いは決済プロバイダーのドメイン内で行えるほうが安心するため、商品更新がよほど頻繁でないかぎり、今回のような静的ページをビルドしておく構成で大体のニーズをカバーできるのでは?と思っています。
また今回自分でCSSを一切書かず、全てMantine UIのコンポーネントを利用しています(最近、スタイルをいかに書かずにデザインできるかにハマっています)。
では早速構築に入っていきます。
1. Stripeの準備
1-1. Stripe登録
Stripeのアカウントを作成します。無料です。
https://dashboard.stripe.com/register
1-2. テストモードでのAPIキー取得
ダッシュボードから下記の操作でAPIキーを取得します。
- 右上 [Developers] クリック
- 左サイドバー [API keys]
- [Viewing test data] をチェック ※テストモードでAPIトークンや商品データが切り替わります
- [Publishable key] と [Secret key] をコピーしておく
- [Publishable key] は
pk_test_XXXX…
のようなフォーマット、 - [Secret key] は
sk_test_XXXX…
というフォーマットです。
- [Publishable key] は
1-3. テスト商品情報の登録
[Viewing test data] にチェックをいれたまま、商品データを2,3件登録しておきます。
これでStripe側の準備は完了です。次にGatsby側の構築に進みます。
2. Gatsby
2-1. アプリの立ち上げと関連パッケージのインストール
※ Node.jsや gatsby-cli
はインストール済みの前提です
% gatsby new stripe-sample % cd stripe-sample % npm install @stripe/stripe-js gatsby-source-stripe @mantine/core @mantine/hooks gatsby-plugin-mantine
2-2. 環境変数と設定情報の追加
アプリのルートディレクトリに環境変数ファイルを追加します。
GATSBY_STRIPE_PUBLISH_KEY=pk_test_XXXX... GATSBY_STRIPE_SECRET_KEY=sk_test_XXXX...
また、先ほど追加した gatsby-source-stripeStripe
プラグインで商品データをソース化してGraphQLでアクセスできるようになるため、その設定をconfigに追加します。ついでにgatsby-plugin-mantine
も追記します。
また、事前定義した環境変数を使えるように require(”dotenv”).config(...)
を加えるのも忘れずに。
gatsby-config.js
は最終的に下記のような記述になるはずです。
require("dotenv").config({ path: `.env.${process.env.NODE_ENV}`, }) module.exports = { ...(略) plugins: [ ...(略) { resolve: `gatsby-source-stripe`, options: { objects: ["Price"], secretKey: process.env.GATSBY_STRIPE_SECRET_KEY, downloadFiles: false, } }, `gatsby-plugin-mantine`, ], }
2-3. Stripe呼び出しコンポーネントの作成
Stripeを呼び出す購入ボタンのコンポーネントを作成します。
import React, { useState } from "react" import { loadStripe } from "@stripe/stripe-js" import { Button, Loader } from '@mantine/core'; let stripePromise const getStripe = () => { if (!stripePromise) { stripePromise = loadStripe(process.env.GATSBY_STRIPE_PUBLISH_KEY) } return stripePromise } const Checkout = ({priceId}) => { const [loading, setLoading] = useState(false) const redirectToCheckout = async event => { event.preventDefault() setLoading(true) const stripe = await getStripe() const { error } = await stripe.redirectToCheckout({ mode: "payment", lineItems: [ { price: priceId, quantity: 1 }, ], successUrl: `http://localhost:8000/page-2/`, cancelUrl: `http://localhost:8000/`, }) if (error) { console.warn("Error:", error) setLoading(false) } } return ( <Button disabled={loading} variant="light" color="blue" fullWidth mt="md" radius="md" onClick={redirectToCheckout} > {loading ? <Loader size="md" variant="dots" /> : "Purchase"} </Button> ) } export default Checkout
上記は購入ボタンのコンポーネントになります。
[Purchase]ボタンをクリックするとredirectToCheckout
ハンドラーが実行され、各商品のprice IDを受け取ってStripeにリクエストを投げ( getStripe()
)、Stripeの決済画面をPromise オブジェクトとして呼び出します。
ボタンの装飾はMatine UIのButtonコンポーネントで行っています。
※Stripe決済成功&失敗時のリダイレクト先の準備は今回省略しています。
2-4. 商品一覧コンポーネントの作成
import React from "react" import { StaticQuery, graphql } from "gatsby" import { Grid, Card, Image, Text, Badge, Group } from '@mantine/core'; import Checkout from "../components/checkout" const Products = () => { return ( <StaticQuery query={graphql` query { allStripePrice(filter: {product: {active: {eq: true}}}) { edges { node { product { name images description } unit_amount currency id } } } } `} render={data => (<Grid>{data.allStripePrice.edges.map(({ node: item }) => { return ( <Grid.Col span={4}> <Card shadow="sm" p="lg" radius="md" withBorder> <Card.Section> <Image src={item.product.images} height={160} /> </Card.Section> <Group position="apart" mt="md" mb="xs"> <Text weight={500}>{item.product.name}</Text> <Badge color="pink" variant="light" size="lg"> {(item.unit_amount / 100).toFixed(2)} {item.currency === "eur" && "€"} </Badge> </Group> <Text size="sm" color="dimmed"> {item.product.description} </Text> <Checkout priceId={item.id} /> </Card> </Grid.Col> ) })}</Grid>)} /> ) } export default Products
StaticQuery
でクエリを投げている通り、 gatsby-source-stripe
でソース化されている商品情報をGraphQLで取得しています。
商品情報のレイアウトは Mantine UIの Card コンポーネントを中心に組んでいます。
あくまで決済処理はセキュアなStripe側でやってもらいつつ、商品情報の取得・表示はフロントの裁量でできるのが気楽でいいです。
2-5. index.jsの編集
上記で作ったコンポーネントを呼び出すようにトップページにも手を加えます。
import * as React from "react" import Layout from "../components/layout" import Seo from "../components/seo" import { Container } from '@mantine/core'; import Products from "../components/products" const IndexPage = () => ( <Layout> <Seo title="Home" /> <Container> <h1>My Products</h1> <Products /> </Container> </Layout> ) export const Head = () => <Seo title="Home" /> export default IndexPage
以上です。
これでアプリを立ち上げてみましょう。
アプリの確認
% gatsby develop
サイトが立ち上がるので http://localhost:8000/
にアクセスします。
[Purchase]ボタンを押すと…
無事 checkout.stripe.com に遷移して購入ウインドウが開きました。
成功!
今回の構成の注意点
Stripeは開発者フレンドリーでドキュメントも充実しているので、テストサイト構築までは非常にスムーズに行えました。
一方で、ECサイトとしての機能拡張を目指すと壁にぶつかるなと感じます。
- 同一商品のバリエーション(例えば色、サイズ違いなど)が登録できない
- 決済には商品コードを必要とし、動的な商品登録が難しい(商品データがStripe上に予め登録されていないといけない)
そのため、商品のバリエーション管理を行おうとした場合、別途CMSなどと組み合わせる形になるかと思います。
最後に
Stripe呼び出し時には商品コード(price ID)をjsonで渡すため、例えばショッピングカート機能を追加して、複数の商品や個数をまとめて購入させることも可能です。
またGraphQLで商品情報に対して好きにクエリしてフロントをカスタマイズできるのも非常に嬉しいです。
またStripeは初期費用不要で決済成立ごとの手数料課金となっているため、スモールスタートしたいビジネスでも薦めやすいと感じました。
クラスメソッドではJamstackの構築支援を行なっています。お気軽にご相談ください。
参考
https://www.gatsbyjs.com/tutorial/ecommerce-tutorial/#example-2-import-skus-via-source-plugin